Skip to content

Version 2.0.0 “Mergenthaler” - #35

Merged
XNinety9 merged 35 commits into
masterfrom
version-2.0
Oct 26, 2026
Merged

XNinety9 merged 35 commits into
masterfrom
version-2.0

Conversation

@XNinety9

Copy link
Copy Markdown
Owner

Printr looks after itself: ready-to-flash image, Wi-Fi setup from the phone, first-run wizard, settings in the app, backup and restore, signed automatic updates. See CHANGELOG.md.

A new settings module lists the variables the administrator may change
from the app (language, default city, printer, code page, Claude key and
model, script token, MQTT, quotas, update check), with a type for each
and validation. They are saved in printr.json and loaded into the
environment at startup right after /etc/printr.env, without overwriting
anything the system sets: such variables are reported as locked. The
rest of the code keeps reading the environment.

GET/PUT /api/settings (administrator only): secrets are only reported
as set or not. After a change, the server exits with code 75 so that
systemd (Restart=on-failure) or Docker restarts it with the new values.
/api/status now gives the default city (PRINTR_CITY).
The first account can pick the language; detection lists USB printers and network printers on port 9100; a test ticket with accents checks the code page before saving. Status reports a pending wizard to the administrator.
…aude key

After the first account, the administrator is guided through finding the printer (USB or network), printing a test ticket and picking the code page from the accents, then the city and an optional Claude key. Settings are saved in one go and the server restarts to apply them.
Printer (with detection and test ticket), language, city, Claude, MQTT, API token, limits and update check, from My account. Values set in /etc/printr.env are shown read-only; secrets are never sent back. A token shorter than 16 characters is refused, since the server would not start with it.
With PRINTR_WIFI_SETUP=1 (the Raspberry Pi image), when no network shows up within a minute of boot, Printr opens a 'Printr-XXXX' hotspot through NetworkManager and prints a ticket with QR codes to join it and open the setup page. The phone is redirected to that page like a captive portal. Once the home Wi-Fi is picked, Printr joins it and prints the app's address; on failure it reopens the hotspot and prints why.
…ssing

After a power cut the Pi often boots before the router. With a Wi-Fi already saved, Printr now waits five minutes before opening its hotspot, says which network it can't find, and tries it again every five minutes while nobody is using the setup page.
Raspberry Pi OS Lite (trixie, arm64) with Printr on port 80 (printr.local), the printer's udev rule, Barnum and the first network connection through a hotspot. image/build.sh builds it locally in Docker (--continue resumes an interrupted build); the release workflow builds it on an ARM runner and attaches it to the release.
…a ticket when it fails

nmcli's hotspot shortcut did not come up on a Pi 3 (brcmfmac). The hotspot is now created with explicit settings (WPA2/CCMP on 2.4 GHz, PMF off, no autoconnect) after turning the Wi-Fi radio on. When the setup cannot start at all, Printr prints why instead of staying silent, and logs each step.
Left to choose, NetworkManager put the hotspot on channel 13, which an iPhone could not join (and which is not allowed in the US). Channel 6 is allowed everywhere.
A new password at every boot made older tickets useless. It is now drawn once and kept in the data directory, like the hotspot name.
NetworkManager also offers WPA-PSK-SHA256 whenever wpa_supplicant supports PMF, even with PMF disabled, and the Raspberry Pi Wi-Fi chip (brcmfmac) then derives a key that phones reject as a wrong password. The image now runs the hotspot with hostapd (plain WPA2, CCMP, channel 6) and dnsmasq (DHCP and captive portal, with the RFC 8910 option) in a printr-hotspot service that printr starts and stops; NetworkManager releases the interface meanwhile and still scans and joins the home Wi-Fi.
The image workflow runs on an ARM runner, either called by the release (attaching the image) or by hand for any branch (as an artifact). DHCP option 114 is dropped: it must point to an HTTPS JSON API, and given the page itself iPhones stopped probing and never showed the captive portal.
… and settings

Both guides get the image, the Wi-Fi setup through the hotspot, the first-run wizard and the settings screen, with screenshots and the setup and test tickets (fictitious data). The READMEs and the site's install section now lead with the image.
docs/demo/setup-session.sh runs Printr in an isolated network namespace (unshare, no sudo) with a fictitious network, fake nmcli and systemctl, and a network printer relayed to the emulator, then captures the Wi-Fi setup page, the wizard and the settings with Playwright, and keeps the hotspot and test tickets. scripts/illustrations.sh runs it in both languages.
Weather can hide the wind and UV line and the sun times, air quality its pollutant and pollen lines; all are shown by default. The date block prints another day, as a set date or an offset in days, to prepare tickets ahead. Suggested by Fabwiss.
A shared birthday list, stored with the data and filled in by anyone from the Birthdays block, is added to each block's own list without duplicates (family, on by default). Suggested by Fabwiss.
PRINTR_CLAUDE_TIMEOUT_SECS (from #26) can be set in the app. ANTHROPIC_BASE_URL stays out on purpose: the Claude key is sent to that address, and the app never hands the key out.
The administrator downloads one JSON file with the accounts, tickets, schedules, history, shopping list, family birthdays, settings and photos, and can restore it; the current state is kept in backups/ first (the last five), sessions are dropped and the print queue is kept. A fresh installation offers to restore from the first account's screen, from the home network only. The file is checked before anything is touched: format version, at least one account, photo names and PNG content.
Opt-in (PRINTR_AUTO_UPDATE=auto, offered by the wizard and in the settings): at night and once the printer is idle, Printr installs a new release only if its SHA256SUMS is signed with the maintainer's minisign key and the binary matches its checksum, runs and reports the expected version; if the new version does not answer after restarting, the guard run before each start puts the previous one back and never retries it. Nothing installs without a key or in a container.

Releases carry the bare binaries; scripts/signer-release.sh signs a release on the maintainer's computer after checking every published checksum; scripts/labo-maj.sh tests five scenarios locally. Ported from the mise-a-jour-auto lab, translated, and fixed for the codename that --version now prints.
Printr accepts a release signed by either public key in RELEASE_KEYS. If the main private key is lost, the spare one, kept offline, signs a version that brings a new main key, and installations keep updating by themselves. The signing script checks the signature against every compiled key, and CONTRIBUTING describes creating and backing up both keys.
…Imager

deploy/get.sh, served by the site as install.sh, downloads the latest archive for the machine, checks the release signature against the release keys with openssl (minisign: Ed25519 over BLAKE2b-512) and the archive's checksum, then runs install.sh. A test keeps its keys in sync with RELEASE_KEYS. The image workflow also publishes printr-imager.json, so Imager can list Printr from a custom content repository.
PRINTR_UNITS=imperial, set in the app, shows temperatures in °F and wind in mph. Forecasts are still read in °C and km/h and converted for display, so the what-to-wear thresholds stay right. Suggested by mi3816 (discussion #34).
Weather, air quality, sun and tonight's sky take the city from the settings when their location is empty. The app no longer fills in Lyon for new blocks: the field shows the default city instead, and a block with neither says where to set one. The setting existed but no block read it.
The block list called cityField() before its declaration, so the app stopped at load (temporal dead zone), which node --check cannot see.
node --check only sees the syntax; the app crashed at load with a constant used before its declaration. Chrome now loads the app from a test server and the login screen must show.
Block examples, app screenshots (first start included), showcase tickets and link preview, in both languages, from scripts/illustrations.sh.
Comment thread src/backup.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
Comment thread src/wifi.rs Dismissed
@XNinety9
XNinety9 merged commit 46f7fa6 into master Oct 26, 2026
11 checks passed
@XNinety9
XNinety9 deleted the version-2.0 branch October 26, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants